package org.simplextensions.di;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.simplextensions.annotations.AnnotationInfo;
import org.simplextensions.registry.IDependencyLocatorRegistry;
import org.simplextensions.registry.IExtensionRegistry;
import org.simplextensions.registry.phaselisteners.IDependencyLocator;

/**
 * @author Mirek Szajowski m.szajowski@gmail.com
 * 
 */
public class DependencyInjector {

	private static final Log log = LogFactory.getLog(DependencyInjector.class);

	private static class FieldInfo {

		public Field field;

		public List<Class<? extends Annotation>> foundAnnotations = new ArrayList<Class<? extends Annotation>>();
	}

	public static void resolveDependency(IExtensionRegistry er, Object targetObject) {
		resolveDependency(er.getExtensionPoint(IDependencyLocatorRegistry.class), targetObject);
	}

	public static void resolveDependency(IDependencyLocatorRegistry dependencyLocatorRegistry, Object targetObject) {
		if (targetObject == null) {
			throw new IllegalArgumentException("Object to injection is null");
		}
		Set<Class<? extends Annotation>> supportedAnnotations = dependencyLocatorRegistry.getSupportedAnnotations();
		List<FieldInfo> fieldsWithAnnotation = getFieldsWithAnnotation(targetObject.getClass(), supportedAnnotations);

		for (FieldInfo fieldInfo : fieldsWithAnnotation) {
			Field field = fieldInfo.field;
			for (Class<? extends Annotation> annotationClass : fieldInfo.foundAnnotations) {
				IDependencyLocator dependencyLocator = dependencyLocatorRegistry.findDependencyLocator(annotationClass);
				if (dependencyLocator != null) {
					Object value = dependencyLocator.getAnnotatedFieldValue(field,
							AnnotationInfo.generateAnnotationInfo(getAnnotation(field, annotationClass)));
					injectService(targetObject, field, value);

				}
			}
		}
	}

	private static Annotation getAnnotation(Field field, Class<? extends Annotation> annotationClass) {
		return field.getAnnotation(annotationClass);
	}

	/**
	 * Returns fields which contains searching annotations
	 * 
	 * @param clazz
	 * @param annotations
	 * @return
	 */
	private static List<FieldInfo> getFieldsWithAnnotation(Class<?> clazz, Set<Class<? extends Annotation>> annotations) {
		List<FieldInfo> fieldsWithAnnotations = new ArrayList<FieldInfo>();
		List<Field> allClassFields = ClassSearcherUtil.getAllClassFields(clazz);
		for (Field field : allClassFields) {
			FieldInfo fieldInfo = getFieldInfo(field, annotations);
			if (fieldInfo.foundAnnotations.size() > 0) {
				fieldsWithAnnotations.add(fieldInfo);
			}
		}

		return fieldsWithAnnotations;
	}

	/**
	 * Return field informations
	 * 
	 * @param field
	 * @param annotations
	 * @return
	 */
	private static FieldInfo getFieldInfo(Field field, Set<Class<? extends Annotation>> annotations) {
		FieldInfo fieldInfo = new FieldInfo();

		for (Annotation annotation : field.getAnnotations()) {
			for (Class<? extends Annotation> searchingAnnotation : annotations) {
				if (annotation.annotationType().equals(searchingAnnotation)) {
					fieldInfo.foundAnnotations.add(searchingAnnotation);
				}
			}
		}
		// if contains at least one of searching annotations
		if (fieldInfo.foundAnnotations.size() > 0) {
			fieldInfo.field = field;
		}
		return fieldInfo;
	}

	/**
	 * Inject value into field
	 * 
	 * @param targetObject
	 * @param field
	 * @param value
	 */
	private static void injectService(Object targetObject, Field field, Object value) {
		log.trace("--- injecting: " + value + " into: " + field.toGenericString());
		field.setAccessible(true);
		try {
			field.set(targetObject, value);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
}
